﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace HaversineDistance
{
	public partial class Start : System.Web.UI.Page
	{

		protected void Page_Load(object sender, EventArgs e)
		{

		}

		protected void btnCalculate_OnClick(object sender, EventArgs e)
		{
			if (Page.IsValid)
			{
				double lat1 = double.Parse(txtLat1.Text);
				double long1 = double.Parse(txtLong1.Text);
				double lat2 = double.Parse(txtLat2.Text);
				double long2 = double.Parse(txtLong2.Text);
				double distanceMiles = CalcHaversineDistance(lat1, long1, lat2, long2, UnitsOfDistance.Miles);
				double distanceKilometers = CalcHaversineDistance(lat1, long1, lat2, long2, UnitsOfDistance.Kilometers);

				lblDistance.Text = string.Format("{0:F2} miles / {1:F2} kms", distanceMiles, distanceKilometers);
			}
		}

		private double ToRadians(double degreeVal)
		{
			return (degreeVal * (Math.PI / 180));
		}
		
		public double CalcHaversineDistance(double lat1, double long1, double lat2, double long2, UnitsOfDistance units)
		{
			const double RadiusOfEarthMiles = 3959.0;
			const double RadiusOfEarthKilometers = 6378.1;
			
			double radianLat1 = ToRadians(lat1);
			double radianLong1 = ToRadians(long1);
			double radianLat2 = ToRadians(lat2);
			double radianLong2 = ToRadians(long2);
			double radianDistanceLat = radianLat1 - radianLat2;
			double radianDistanceLong = radianLong1 - radianLong2;
			double sinLat = Math.Sin(radianDistanceLat / 2.0);
			double sinLong = Math.Sin(radianDistanceLong / 2.0);
			double a = Math.Pow(sinLat, 2.0) + Math.Cos(radianLat1) * Math.Cos(radianLat2) * Math.Pow(sinLong, 2.0);

			double radius = RadiusOfEarthMiles; // miles are the default units
			switch (units) // I used a switch statement in case other units are accepted, i.e. meters or feet.
			{
				case UnitsOfDistance.Miles:
					break;
				case UnitsOfDistance.Kilometers:
					radius = RadiusOfEarthKilometers;
					break;
			}

			double distance = radius * 2 * Math.Asin(Math.Min(1, Math.Sqrt(a)));

			return distance;
		}

		protected void cvLatitude_ServerValidate(object source, ServerValidateEventArgs args)
		{
			args.IsValid = IsValidLatitude(txtLat1.Text) && IsValidLatitude(txtLat2.Text); 
		}

		protected void cvLongitude_ServerValidate(object source, ServerValidateEventArgs args)
		{
			args.IsValid = IsValidLongitude(txtLong1.Text) && IsValidLatitude(txtLong2.Text);
		}

		private bool IsValidLatitude(string latText)
		{
			double? lat = TryParse(latText);
			if (lat != null)
			{
				return ((lat >= -90) || (lat <= 90));
			}

			return false;
		}

		private bool IsValidLongitude(string longitudeText)
		{
			double? longitude = TryParse(longitudeText);
			if (longitude != null)
			{
				return ((longitude >= -180) || (longitude <= 180));
			}

			return false;
		}

		private double? TryParse(string s)
		{
			double ret;
			if (double.TryParse(s, out ret))
			{
				return ret;
			}
			else
			{
				return null;
			}
		}

	}

	
	public class GeoDistance
	{
		public double distanceInMiles { get; set; }
		public double distanceInKilometers { get; set; }

		public GeoDistance(double distanceInMiles, double distanceInKilometers)
		{
			this.distanceInMiles = distanceInMiles;
			this.distanceInKilometers = distanceInKilometers;
		}
	}

	public enum UnitsOfDistance : int
	{
		Miles,
		Kilometers
	}
}
